#ifndef __Vector3_hpp__
#define __Vector3_hpp__

#include "Vector2.hpp"

namespace ge {

//----------------------------------------------------------------------------//
//
//----------------------------------------------------------------------------//

class Vector3
{
 public:

     Vector3( void );

     Vector3( const Vector3& _v );

     Vector3( const Vector2& _v, float _z = 0.0f );

     Vector3( float _s );

     Vector3( float _x, float _y, float _z = 0.0f );

     Vector3( const float _v[3] );

     Vector3& operator = ( const Vector3& _v );

     Vector3& operator = ( const Vector2& _v );

     Vector3& operator = ( float _s );

     float& operator [] ( uint _index );

     const float& operator [] ( uint _index ) const;

     float* operator * ( void );

     const float* operator * ( void ) const;

     operator Vector2 ( void ) const;

     bool operator == ( const Vector3& _v ) const;

     bool operator < ( const Vector3& _v ) const;

     bool operator <= ( const Vector3& _v ) const;

     bool operator != ( const Vector3& _v ) const;

     bool operator > ( const Vector3& _v ) const;

     bool operator >= ( const Vector3& _v ) const;

     Vector3 operator - ( void ) const;

     const Vector3& operator + ( void ) const;

     Vector3 operator + ( const Vector3& _v ) const;

     Vector3 operator - ( const Vector3& _v ) const;

     Vector3 operator * ( const Vector3& _v ) const;

     Vector3 operator / ( const Vector3& _v ) const;

     Vector3 operator + ( float _s ) const;

     Vector3 operator - ( float _s ) const;

     Vector3 operator * ( float _s ) const;

     Vector3 operator / ( float _s ) const;

     friend Vector3 operator + ( float _s, const Vector3& _v );

     friend Vector3 operator - ( float _s, const Vector3& _v );

     friend Vector3 operator * ( float _s, const Vector3& _v );

     friend Vector3 operator / ( float _s, const Vector3& _v );

     Vector3& operator += ( const Vector3& _v );

     Vector3& operator -= ( const Vector3& _v );

     Vector3& operator *= ( const Vector3& _v );

     Vector3& operator /= ( const Vector3& _v );

     Vector3& operator += ( float _s );

     Vector3& operator -= ( float _s );

     Vector3& operator *= ( float _s );

     Vector3& operator /= ( float _s );

     Vector3& Set( float _s );

     Vector3& Set( float _x, float _y, float _z = 0.0f );

     Vector3& FromPtr( const float _v[3] );

     Vector3& Set( const Vector3& _v );

     Vector3& Set( const Vector2& _v, float _z = 0.0f );

     Vector3& SetZero( void );

     Vector3& SetIdentity( void );

     Vector3& SetAxis( uint _axis, float _value );

     float& Axis( uint _axis );

     const float& Axis( uint _axis ) const;

     Vector3& InsertVector2( const Vector2& _v );

     float Length( void ) const;

     float LengthSq( void ) const;

     float LengthInv( void ) const;

     float Distance( const Vector3& _v ) const;

     float DistanceSq( const Vector3& _v ) const;

     float Dot( const Vector3& _v ) const;

     float AbsDot( const Vector3& _v ) const;

     Vector3 Cross( const Vector3& _v ) const;

     Vector3& Normalize( void );

     Vector3& SafeNormalize( void );

     Vector3& SafeNormalize( float& _length );

     Vector3 NormalizedCopy( void ) const;

     Vector3& SetMin( const Vector3& _v );

     Vector3& SetMin( const Vector3& _v1, const Vector3& _v2 );

     Vector3& SetMax( const Vector3& _v );

     Vector3& SetMax( const Vector3& _v1, const Vector3& _v2 );

     Vector3 MidPoint( const Vector3& _v ) const;

     Vector3 Perpendicular( void ) const;

     float Angle( const Vector3& _v ) const;

     Vector3 Reflect( const Vector3& _n ) const;

     Vector3 NormalizedReflect( const Vector3& _n ) const;

     bool EqualPosition( const Vector3& _v, float _tolerance = Math::Epsilon ) const;

	    bool ClosePosition( const Vector3& _v, float _tolerance = Math::Epsilon ) const;

	    bool EqualDirection( const Vector3& _v,	float _tolerance = Math::ToRadians( Math::Epsilon ) ) const;

 public:

     static const Vector3 Zero;
     static const Vector3 UnitX;
     static const Vector3 UnitY;
     static const Vector3 UnitZ;
     static const Vector3 NegativeUnitX;
     static const Vector3 NegativeUnitY;
     static const Vector3 NegativeUnitZ;
     static const Vector3 UnitScale;
     static const Vector3 Identity;

 public:

     union
     {
          struct { float x,y,z; };
          float v[3];
     };
};

//----------------------------------------------------------------------------//
//
//----------------------------------------------------------------------------//

//----------------------------------------------------------------------------//
inline Vector3::Vector3( void ) 
{
}
//----------------------------------------------------------------------------//
inline Vector3::Vector3( const Vector3& _v ) : x( _v.x ), y( _v.y ), z( _v.z )
{
}
//----------------------------------------------------------------------------//
inline Vector3::Vector3( const Vector2& _v, float _z ) : x( _v.x ), y( _v.y ), z( _z )
{
}
//----------------------------------------------------------------------------//
inline Vector3::Vector3( float _s ) : x( _s ), y( _s ), z( _s )
{
}
//----------------------------------------------------------------------------//
inline Vector3::Vector3( float _x, float _y, float _z ) : x( _x ), y( _y ), z( _z )
{
}
//----------------------------------------------------------------------------//
inline Vector3::Vector3( const float _v[3] ) : x( _v[0] ), y( _v[1] ), z( _v[2] )
{
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator = ( const Vector3& _v )
{
     x = _v.x;
     y = _v.y;
     z = _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator = ( const Vector2& _v )
{
     x = _v.x;
     y = _v.y;
     z = 0.0f;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator = ( float _s )
{
     x = _s;
     y = _s;
     z = _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline float& Vector3::operator [] ( uint _index )
{
     return v[_index];
}
//----------------------------------------------------------------------------//
inline const float& Vector3::operator [] ( uint _index ) const
{
     return v[_index];
}
//----------------------------------------------------------------------------//
inline float* Vector3::operator * ( void )
{
     return v;
}
//----------------------------------------------------------------------------//
inline const float* Vector3::operator * ( void ) const
{
     return v;
}
//----------------------------------------------------------------------------//
inline Vector3::operator Vector2 ( void ) const
{
     return Vector2( x, y );
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator == ( const Vector3& _v ) const
{
     return x == _v.x && y == _v.y && z == _v.z;
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator < ( const Vector3& _v ) const
{
     return x < _v.x && y < _v.y && z < _v.z;
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator <= ( const Vector3& _v ) const
{
     return x <= _v.x && y <= _v.y && z <= _v.z;
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator != ( const Vector3& _v ) const
{
     return !(*this == _v);
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator > ( const Vector3& _v ) const
{
     return !(*this <= _v);
}
//----------------------------------------------------------------------------//
inline bool Vector3::operator >= ( const Vector3& _v ) const
{
     return !(*this < _v);
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator - ( void ) const
{
     return Vector3( -x, -y, -z );
}
//----------------------------------------------------------------------------//
inline const Vector3& Vector3::operator + ( void ) const
{
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator + ( const Vector3& _v ) const
{
     return Vector3( x + _v.x, y + _v.y, z + _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator - ( const Vector3& _v ) const
{
     return Vector3( x - _v.x, y - _v.y, z - _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator * ( const Vector3& _v ) const
{
     return Vector3( x * _v.x, y * _v.y, z * _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator / ( const Vector3& _v ) const
{
     return Vector3( x / _v.x, y / _v.y, z / _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator + ( float _s ) const
{
     return Vector3( x + _s, y + _s, z + _s );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator - ( float _s ) const
{
     return Vector3( x - _s, y - _s, z - _s );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator * ( float _s ) const
{
     return Vector3( x * _s, y * _s, z * _s );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::operator / ( float _s ) const
{
     _s = 1.0f / _s;
     return Vector3( x * _s, y * _s, z * _s );
}
//----------------------------------------------------------------------------//
inline Vector3 operator + ( float _s, const Vector3& _v )
{
     return Vector3( _s + _v.x, _s + _v.y, _s + _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 operator - ( float _s, const Vector3& _v )
{
     return Vector3( _s - _v.x, _s - _v.y, _s - _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 operator * ( float _s, const Vector3& _v )
{
     return Vector3( _s * _v.x, _s * _v.y, _s * _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 operator / ( float _s, const Vector3& _v )
{
     return Vector3( _s / _v.x, _s / _v.y, _s / _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator += ( const Vector3& _v )
{
     x += _v.x;
     y += _v.y;
     z += _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator -= ( const Vector3& _v )
{
     x -= _v.x;
     y -= _v.y;
     z -= _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator *= ( const Vector3& _v )
{
     x *= _v.x;
     y *= _v.y;
     z *= _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator /= ( const Vector3& _v )
{
     x /= _v.x;
     y /= _v.y;
     z /= _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator += ( float _s )
{
     x += _s;
     y += _s;
     z += _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator -= ( float _s )
{
     x -= _s;
     y -= _s;
     z -= _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator *= ( float _s )
{
     x *= _s;
     y *= _s;
     z *= _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::operator /= ( float _s )
{
     x /= _s;
     y /= _s;
     z /= _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::Set( float _s )
{
     x = _s;
     y = _s;
     z = _s;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::Set( float _x, float _y, float _z )
{
     x = _x;
     y = _y;
     z = _z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::FromPtr( const float _v[3] )
{
     x = _v[0];
     y = _v[1];
     z = _v[2];
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::Set( const Vector3& _v )
{
     x = _v.x;
     y = _v.y;
     z = _v.z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::Set( const Vector2& _v, float _z )
{
     x = _v.x;
     y = _v.y;
     z = _z;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetZero( void )
{
     x = 0.0f;
     y = 0.0f;
     z = 0.0f;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetIdentity( void )
{
     x = 0.0f;
     y = 0.0f;
     z = 0.0f;
     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetAxis( uint _axis, float _value ) 
{
     v[_axis] = _value;
     return *this;
}
//----------------------------------------------------------------------------//
inline float& Vector3::Axis( uint _axis ) 
{
     return v[_axis];
}
//----------------------------------------------------------------------------//
inline const float& Vector3::Axis( uint _axis ) const 
{
     return v[_axis];
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::InsertVector2( const Vector2& _v )
{
     x = _v.x;
     y = _v.y;
     return *this;
}
//----------------------------------------------------------------------------//
inline float Vector3::Length( void ) const
{
     return Math::Sqrt( x * x + y * y + z * z );
}
//----------------------------------------------------------------------------//
inline float Vector3::LengthSq( void ) const
{
     return x * x + y * y + z * z;
}
//----------------------------------------------------------------------------//
inline float Vector3::LengthInv( void ) const
{
     return Math::RSqrt( x * x + y * y + z * z );
}
//----------------------------------------------------------------------------//
inline float Vector3::Distance( const Vector3& _v ) const
{
     return ( ( *this -_v ).Length() );
}
//----------------------------------------------------------------------------//
inline float Vector3::DistanceSq( const Vector3& _v ) const
{
     return ( ( *this -_v ).LengthSq() );
}
//----------------------------------------------------------------------------//
inline float Vector3::Dot( const Vector3& _v ) const
{
     return x * _v.x + y * _v.y + z * _v.z;
}
//----------------------------------------------------------------------------//
inline float Vector3::AbsDot( const Vector3& _v ) const
{
     return Math::Abs( x * _v.x ) + Math::Abs( y * _v.y ) + Math::Abs( z * _v.z );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::Cross( const Vector3& _v ) const
{
     return Vector3( y * _v.z - z * _v.y, z * _v.x - x * _v.z, x * _v.y - y * _v.x );
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::Normalize( void )
{
    return (*this *= LengthInv());
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SafeNormalize( void )
{
     float _length = LengthSq();

     if( _length > Math::EpsilonSq )
     {
         _length = Math::RSqrt( _length );
         x *= _length;
         y *= _length;
         z *= _length;
     }

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SafeNormalize( float& _length )
{
     _length = LengthSq();

     if( _length > Math::EpsilonSq )
     {
         _length = Math::RSqrt( _length );
         x *= _length;
         y *= _length;
         z *= _length;
     }

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::NormalizedCopy( void ) const
{
     return Vector3( *this ).Normalize();
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetMin( const Vector3& _v )
{
     if( _v.x < x ) x = _v.x;
     if( _v.y < y ) y = _v.y;
     if( _v.z < z ) z = _v.z;

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetMin( const Vector3& _v1, const Vector3& _v2 )
{
     x = Min( _v1.x, _v2.x );
     y = Min( _v1.y, _v2.y );
     z = Min( _v1.z, _v2.z );

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetMax( const Vector3& _v )
{
     if( _v.x > x ) x = _v.x;
     if( _v.y > y ) y = _v.y;
     if( _v.z > z ) z = _v.z;

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3& Vector3::SetMax( const Vector3& _v1, const Vector3& _v2 )
{
     x = Max( _v1.x, _v2.x );
     y = Max( _v1.y, _v2.y );
     z = Max( _v1.z, _v2.z );

     return *this;
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::MidPoint( const Vector3& _v ) const
{
    return ( (*this) + _v ) * 0.5f;
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::Perpendicular( void ) const
{
     Vector3 _perp = Cross( UnitX );
     if( _perp.LengthSq() <= Math::EpsilonSq ) _perp = Cross( UnitY );
     return _perp.Normalize();
}
//----------------------------------------------------------------------------//
inline float Vector3::Angle( const Vector3& _v ) const
{
     float _lp = Length() * _v.Length();
     if( _lp <= Math::EpsilonSq ) _lp = Math::EpsilonSq;
     return Math::ACos( Math::Clamp( Dot( _v ) / _lp, -1.0f, 1.0f ) );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::Reflect( const Vector3& _n ) const
{
     return Vector3( (*this) - ( 2.0f * Dot( _n ) * _n ) );
}
//----------------------------------------------------------------------------//
inline Vector3 Vector3::NormalizedReflect( const Vector3& _n ) const
{
     return Reflect( _n ).Normalize();
}
//----------------------------------------------------------------------------//
inline bool Vector3::EqualPosition( const Vector3& _v, float _tolerance ) const
{
     return ( Math::Equals( x, _v.x, _tolerance ) && Math::Equals( y, _v.y, _tolerance ) && Math::Equals( z, _v.z, _tolerance ) );
}
//----------------------------------------------------------------------------//
inline bool Vector3::ClosePosition( const Vector3& _v, float _tolerance ) const
{
     return DistanceSq( _v ) <= ( ( LengthSq() + _v.LengthSq() ) * _tolerance );
}
//----------------------------------------------------------------------------//
inline bool Vector3::EqualDirection( const Vector3& _v,	float _tolerance ) const
{
     return Math::Abs( Math::ACos( Dot( _v ) ) ) <= _tolerance;
}
//----------------------------------------------------------------------------//

} // namespace ge

#endif//__Vector3_hpp__
